/*
 * To Do:
 * - move input listener to player
 * - track health, remove when collision
 * - create different "interactable control" types
 */


package net.projectRPG.src;

import com.jme3.light.LightList;
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.math.Quaternion;

import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;

import com.jme3.niftygui.NiftyJmeDisplay;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;

import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;

import com.jme3.scene.Spatial;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;

import java.util.ArrayList;
import java.util.List;

import com.jme3.util.SkyFactory;

import com.jme3.light.DirectionalLight;
import com.jme3.post.FilterPostProcessor;
import com.jme3.math.ColorRGBA;
import com.jme3.water.WaterFilter;
import java.util.HashSet;
import java.util.Set;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.post.SceneProcessor;

import net.projectRPG.src.MenuScreen;

import com.jme3.audio.AudioNode;
import com.jme3.audio.Filter;
import com.jme3.audio.LowPassFilter;

import java.util.logging.Logger;
import java.util.logging.Level;

import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.math.Matrix3f;
import com.jme3.math.Ray;

import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CylinderCollisionShape;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import net.projectRPG.src.InteractableControl;

public class Main extends SimpleApplication implements ScreenController, ActionListener   {

    // determines whether or not game is running
    Boolean isRunning = true;
    // for Nifty GUI
    private Nifty nifty;
    public Player player;
    private Vector3f walkDirection = new Vector3f();
    public BulletAppState bulletAppState;
    public MenuScreen menuScreen;
    public Area currentArea;
    public FilterPostProcessor areaProcessor;
    AudioNode waves;
    // objects you can interact with
    public Node interactable; 
    private static Main main;
    public float globalGravity = 60.0f;
    public float globalFallSpeed = 120.0f;


    
    public static void main(String[] args) {
        Main app = new Main();
        app.start();
        main = app;
    }

    @Override
    public void simpleInitApp() {
        
        /** Set up Physics */
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);        
        //bulletAppState.getPhysicsSpace().enableDebug(assetManager);       
        bulletAppState.getPhysicsSpace().setAccuracy(0.005f);
        
        // create a player object
        //
        CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.0f, 2.0f, 1);
        player = new Player(capsuleShape, 0.05f, this);
        player.setJumpSpeed(12);
        player.setFallSpeed(main.globalFallSpeed);
        player.setGravity(main.globalGravity);
        player.setMaxSlope(0.5f);
        bulletAppState.getPhysicsSpace().add(player); 
        
        waves = new AudioNode(assetManager, "Sounds/Ocean Waves.ogg", false);
        waves.setLooping(true);
        waves.setDryFilter(new LowPassFilter(1, 1));            

        audioRenderer.playSource(waves);        
        
        // map keys
        setUpKeys();          
        
        // set up area processor for later effects
        areaProcessor = new FilterPostProcessor(this.getAssetManager());
        
        // create menu screen
        menuScreen = new MenuScreen(this);
        DisplayMenu();
    }
    
      private ActionListener actionListener = new ActionListener() {
        public void onAction(String name, boolean keyPressed, float tpf) {
          if (name.equals("MainMenu") && !keyPressed) {
            DisplayMenu();
          }
          if (name.equals("Use") && !keyPressed) {
            UseObject();
          }  
          // temporary create object code
          if (name.equals("CreateObject") && !keyPressed) {
            float sz = 5.0f;
            Vector3f camPos = cam.getLocation();
            Vector3f camDir = cam.getDirection();
            Vector3f newPos = new Vector3f();
            newPos.setX(camPos.getX() + sz * camDir.getX());
            newPos.setY(camPos.getY() + sz * camDir.getY());
            newPos.setZ(camPos.getZ() + sz * camDir.getZ());

            Node node = new Node();
            
            Spatial box = assetManager.loadModel("Models/barrel.j3o"); 
            box.setLocalTranslation(0.f, -0.7f, 0f);
            node.attachChild(box);
            box.setShadowMode(ShadowMode.CastAndReceive);
            CylinderCollisionShape shape = new CylinderCollisionShape(new Vector3f(0.6f, 0.6f, 0.6f), 1);
            InteractableControl control = new InteractableControl(shape, 2f, 0.4f, main);
            node.addControl(control);
            // add to terrain node to get lighting working
            Node terrainOnlyNode = (Node) rootNode.getChild("Terrain");             
            terrainOnlyNode.attachChild(node);
            

            bulletAppState.getPhysicsSpace().add(control);  
            control.setPhysicsLocation(newPos);
              System.out.println("Creating object at " + control.getPhysicsLocation());            

          }            
        }
      };    

    public void DisplayMenu() {
            NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
                                                              inputManager,
                                                              audioRenderer,
                                                              guiViewPort);
            nifty = niftyDisplay.getNifty();
            nifty.fromXml("Interface/MainMenu.xml", "start", menuScreen);
            guiViewPort.addProcessor(niftyDisplay);    
            inputManager.setCursorVisible(true);
            this.bulletAppState.setEnabled(false);  
    }
      
    public void bind(Nifty nifty, Screen screen) {
        System.out.println("bind( " + screen.getScreenId() + ")");
    }

    public void onStartScreen() {
        System.out.println("onStartScreen");
    }

    public void onEndScreen() {
        System.out.println("onEndScreen");
    }

 
// Bind keys to actions
  private void setUpKeys() {
    inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
    inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
    inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
    inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
    inputManager.addMapping("Jump", new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping("Crouch", new KeyTrigger(KeyInput.KEY_LCONTROL));
    inputManager.addMapping("Run", new KeyTrigger(KeyInput.KEY_LSHIFT));
    inputManager.addMapping("Use", new KeyTrigger(KeyInput.KEY_E));
    inputManager.addMapping("CreateObject", new KeyTrigger(KeyInput.KEY_O));
    inputManager.addListener(this, "Left");
    inputManager.addListener(this, "Right");
    inputManager.addListener(this, "Up");
    inputManager.addListener(this, "Down");
    inputManager.addListener(this, "Jump");
    inputManager.addListener(this, "Run");
    inputManager.addListener(this, "Crouch");    
    inputManager.addListener(actionListener, new String[]{"Use"});   
    inputManager.addListener(actionListener, new String[]{"CreateObject"});      
    // remove the existing escape key function
    this.getInputManager().deleteMapping( SimpleApplication.INPUT_MAPPING_EXIT );
    inputManager.addMapping("MainMenu",     new KeyTrigger(KeyInput.KEY_ESCAPE));        
    inputManager.addListener(actionListener, new String[]{"MainMenu"});      
  }
 
  // Trigger events when actions occur
  public void onAction(String binding, boolean value, float tpf) {
    if (binding.equals("Left")) {
      if (value) { player.left = true; } else { player.left = false; }
    } else if (binding.equals("Right")) {
      if (value) { player.right = true; } else { player.right = false; }
    } else if (binding.equals("Up")) {
      if (value) { player.up = true; } else { player.up = false; }
    } else if (binding.equals("Down")) {
      if (value) { player.down = true; } else { player.down = false; }
    } else if (binding.equals("Crouch")) {
      if (value) { player.crouch = true; } else { player.crouch = false; }      
    } else if (binding.equals("Run")) {
      if (value) { player.run = true; } else { player.run = false; }        
    } else if (binding.equals("Jump")) {
      if (value) { player.jump = true; } else { player.jump = false; }        

    }
  }
 
  // try to pick up object
  public void UseObject()
  {
          if(player.carriedObject != null)
          {
              RigidBodyControl object = player.carriedObject;
              if(object != null)
                object.applyForce(cam.getDirection().multLocal(0.1f),Vector3f.ZERO);
              
              player.carriedObject = null;
              return;
          }
      
      System.out.println("using object");
        CollisionResults results = new CollisionResults();
        // 2. Aim the ray from cam loc to cam direction.
        Ray ray = new Ray(cam.getLocation(), cam.getDirection());
        // 3. Collect intersections between Ray and Shootables in results list.
        rootNode.collideWith(ray, results);
        // 4. Print the results
        for (int i = 0; i < results.size(); i++) {
          // For each hit, we know distance, impact point, name of geometry.
          float dist = results.getCollision(i).getDistance();
          Vector3f pt = results.getCollision(i).getContactPoint();
          String hit = results.getCollision(i).getGeometry().getName();
          if (results.size() > 0){
            CollisionResult closest = results.getClosestCollision();
            if(dist <= player.maxReach)
            {
                Geometry collisionGeometry = closest.getGeometry();
                //try and find MyControl directly on Geometry
                InteractableControl control = collisionGeometry.getControl(InteractableControl.class);
                //if no control is found, find it in some parent of this Geometry, should be the model we look for if it has this control.
                if(control == null){
                Spatial spat = collisionGeometry.getParent();
                  while(control == null && spat != null){
                    control = spat.getControl(InteractableControl.class);
                    spat = spat.getParent();
                  }
                }                

                player.carriedObject = control;
            }
          }
        }
  }
  
  
// Perform walking, other updates
  @Override
  public void simpleUpdate(float tpf) {
 
      
    // if physics are paused, don't do anything
    if(main.bulletAppState.isEnabled() == true)
    {
        player.simpleUpdatePlayer(tpf);
    }
      
    // limit camera tilt
    float rotation = cam.getDirection().y;
    float maxTilt = .8f;
    if(rotation > maxTilt || rotation < -maxTilt)
    {
        if(rotation > maxTilt)
        {
            rotation = maxTilt;
        }else{
            rotation = -maxTilt;
        }
        Vector3f newDirection = new Vector3f(cam.getDirection().x, rotation, cam.getDirection().z);
        cam.lookAtDirection(newDirection, Vector3f.UNIT_Y);
    }
        
    // if underwater, change sound
    /*
    if(cam.getLocation().y < currentArea.waterLevel )
    {
        waves.setDryFilter(new LowPassFilter(0.5f, 0.1f));
        player.setGravity(0);
        player.setFallSpeed(6.0f);
    }else{
        waves.setDryFilter(new LowPassFilter(1, 1f));
        player.setGravity(30);
        player.setFallSpeed(60);
    }*/
  }    
  
}
